package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Override
    public PageResult search(RequestParams params) throws IOException {
        //准备Request
        SearchRequest request = new SearchRequest("hotel");
        //准备Dsl
//        if (StringUtils.isNotBlank(params.getKey())) {
//            request.source().query(QueryBuilders.matchQuery("all", "如家"));//根据关键字查询
//        } else {
//            request.source().query(QueryBuilders.matchAllQuery());//没有查询所有
//        }
        buildBasicQuery(params, request);
        //排序
        request.source().sort("price", SortOrder.DESC);
        //根据一个坐标查询
        if (StringUtils.isNotBlank(params.getLocation())) {
            request.source().sort(SortBuilders.geoDistanceSort("location",
                    new GeoPoint(params.getLocation())).unit
                    (DistanceUnit.CENTIMETERS).order
                    (SortOrder.ASC));
        }
        //分页 size from
        Integer page = params.getPage();
        Integer size = params.getSize();
        request.source().from((page - 1) * size).size(10);

        //准备高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<em style='color:red'>");
        highlightBuilder.postTags("</em>");
        highlightBuilder.field("name");
        highlightBuilder.requireFieldMatch(false);
        request.source().highlighter(highlightBuilder);
        //发送请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        //响应请求
        return handleResponse(response);
    }

    @Override
    public void saveOrUpdateEs(String jsonString) {
        Hotel hotel = JSON.parseObject(jsonString, Hotel.class);
        HotelDoc hotelDoc = new HotelDoc(hotel);

        IndexRequest request = new IndexRequest("hotel"); //索引库名称
        request.id(hotel.getId().toString());
        String jsonStr = JSON.toJSONString(hotelDoc);
        request.source(jsonStr, XContentType.JSON);
        try {
            IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void deleteEs(String id) {

        DeleteRequest request = new DeleteRequest("hotel",id); //索引库名称
        try {
            DeleteResponse response = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private PageResult handleResponse(SearchResponse response) {
        List<HotelDoc> hotels = new ArrayList<>();
        //解析响应
        SearchHits hits = response.getHits();
        //获取总条数
        long total = hits.getTotalHits().value;
        System.out.println("共搜到" + total + "条数据");
        //遍历数组存入文档
        SearchHit[] searchHits = hits.getHits();
        for (SearchHit searchHit : searchHits) {
            //获取文档source
            String sourceAsString = searchHit.getSourceAsString();
            //反序列化
            HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
            //获取高亮结果
            Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            if (!CollectionUtils.isEmpty(highlightFields)) {
                //根据字段名获取高亮结果
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField != null) {
                    Text[] fragments = highlightField.fragments();
                    if (fragments != null && fragments.length > 0) {
                        //获取高亮值
                        String name = highlightField.getFragments()[0].toString();
                        //覆盖非高亮结果
                        hotelDoc.setName(name);
                    }
                }

                }
            Object[] sortValues = searchHit.getSortValues();
            if (sortValues!=null&&sortValues.length>0){
                hotelDoc.setDistance(sortValues[0]);
            }
            hotels.add(hotelDoc);
        }
        PageResult pageResult = new PageResult(hits.getTotalHits().value, hotels);
        return pageResult;
    }

    private void buildBasicQuery(RequestParams params, SearchRequest request) {
        // 构建BooleanQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 关键字搜索
        if (StringUtils.isNotBlank(params.getKey())) {

            boolQuery.must(QueryBuilders.matchQuery("all", params.getKey()));
        } else {
            boolQuery.must(QueryBuilders.matchAllQuery());

        }
        // 城市条件
        if (StringUtils.isNotBlank(params.getCity())) {
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        // 品牌条件
        if (StringUtils.isNotBlank(params.getBrand())) {
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        // 星级条件
        if (StringUtils.isNotBlank(params.getStarName())) {
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
        // 价格条件
        if (params.getMinPrice() != null && params.getMaxPrice() != null) {
            boolQuery.filter(QueryBuilders
                    .rangeQuery("price")
                    .gte(params.getMinPrice())
                    .lte(params.getMaxPrice())
            );
        }
        // 算分控制
        FunctionScoreQueryBuilder functionScoreQuery =
                QueryBuilders.functionScoreQuery(
                        // 原始查询，相关性算分的查询
                        boolQuery,
                        // function score的数组
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                // 其中的一个function score 元素
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                        // 过滤条件
                                        QueryBuilders.termQuery("isAD", true),
                                        // 算分函数
                                        ScoreFunctionBuilders.weightFactorFunction(10)
                                )
                        });
        // 放入source
        request.source().query(functionScoreQuery);
    }
}
